//
// Created by Administrator on 2021/8/27.
//

#include "Application/Application.h"

Application* Application::app_ = nullptr;

void Application::Run()
{
    app_ = this;

    if(!glfwInit())
    {
        ERROR("glfwInit failed!")
        glfwTerminate();
        return;
    }

    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
#ifdef MAC_OS
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
#else
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5);
#endif
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef MAC_OS
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
#endif
    window_ = glfwCreateWindow(width_, height_, title_.c_str(), nullptr, nullptr);
    if(!window_)
    {
        ERROR("glfwCreateWindow failed!")
        glfwTerminate();
        return;
    }

    glfwSetFramebufferSizeCallback(window_, glfwResize);
    glfwSetCursorPosCallback(window_, glfwCursorPos);
    glfwSetKeyCallback(window_, glfwKey);
    glfwSetInputMode(window_, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
    glfwMakeContextCurrent(window_);

    if(!gladLoadGLLoader(GLADloadproc(glfwGetProcAddress)))
    {
        ERROR("gladLoadGLLoader failed!")
        glfwDestroyWindow(window_);
        glfwTerminate();
        return;
    }

    double curPosX, curPosY;
    glfwGetCursorPos(window_, &curPosX, &curPosY);
    lastCursorPosX = static_cast<float>(curPosX);
    lastCursorPosY = static_cast<float>(curPosY);
    OnSetCameraMatrix(Vector3(0.0f, 0.0f, 10.0f), Vector3::Back(), Vector3::Up());
    OnSetProjectionMatrix(width_, height_, 0.1f, 1000.0f, 50.0f);

    OnCreate();

    while(!glfwWindowShouldClose(window_))
    {
        OnRender();
        glfwSwapBuffers(window_);
        glfwPollEvents();
    }

    OnDestroy();

    glfwDestroyWindow(window_);
    glfwTerminate();
}

void Application::MoveCameraByKey(int key)
{
    // move by w/s/a/d
    float speed = 0.1f;
    if(key == GLFW_KEY_W)
    {
        camPos += camForward * speed;
    }
    if(key == GLFW_KEY_S)
    {
        camPos -= camForward * speed;
    }
    if(key == GLFW_KEY_A)
    {
        camPos -= camRight * speed;
    }
    if(key == GLFW_KEY_D)
    {
        camPos += camRight * speed;
    }
    if(key == GLFW_KEY_UP)
    {
        camPos += camUp * speed;
    }
    if(key == GLFW_KEY_DOWN)
    {
        camPos -= camUp * speed;
    }
    OnSetCameraMatrix(camPos, camForward, camUp);
}

void Application::RotateCameraByCursor(float x, float y)
{
    float offsetX = (x - lastCursorPosX) * 0.1f * DEG_2_RAD * -1;
    float offsetY = (y - lastCursorPosY) * 0.1f * DEG_2_RAD * -1;
    camForward.x() = camForward.x() * cosf(offsetX) + camForward.z() * sinf(offsetX);
    camForward.z() = camForward.x() * -sinf(offsetX) + camForward.z() * cosf(offsetX);
    camForward.y() = camForward.y() * cosf(offsetY) + camForward.z() * -sinf(offsetY);
    camForward.z() = camForward.y() * sinf(offsetY) + camForward.z() * cosf(offsetY);
    camForward.Norm();
    OnSetCameraMatrix(camPos, camForward, camUp);
    lastCursorPosX = x;
    lastCursorPosY = y;
}

void Application::OnSetCameraMatrix(const Vector3& pos, const Vector3& gaze, const Vector3& up)
{
    camPos = pos;
    Vector3 w = -gaze;
    Vector3 u = up.Cross(w);
    Vector3 v = w.Cross(u);
    camForward = gaze;
    camUp = up;
    camRight = u;
    auto r = Matrix4x4(u.x(), u.y(), u.z(), 0.0f,
                       v.x(), v.y(), v.z(), 0.0f,
                       w.x(), w.y(), w.z(), 0.0f);
    cameraMatrix = r * Matrix4x4(1, 0, 0, -pos.x(),
                             0, 1, 0, -pos.y(),
                             0, 0, 1, -pos.z());
}

void Application::OnSetProjectionMatrix(int w, int h, float near, float far, float fov)
{
    float aspect = float(w) / float(h);
    float n = abs(near);
    float f = abs(far);
    float t = tanf(fov * 0.5f * DEG_2_RAD) * near;
    float b = -t;
    float r = t * aspect;
    float l = -r;
    projectMatrix = Matrix4x4(2 * n / (r - l), 0, (r + l) / (r - l), 0,
                              0, 2 * n / (t - b), (t + b) / (t - b), 0,
                              0, 0, (n + f) / (n - f), 2 * f * n / (n - f),
                              0, 0, -1, 0);
}